/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this 
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the dnAnalytics Project nor the names of its contributors
 *   may be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using dnAnalytics.LinearAlgebra.Solvers.Preconditioners;
using dnAnalytics.Math;

namespace dnAnalytics.LinearAlgebra.Solvers.Iterative
{
    /// <summary>
    /// A Bi-Conjugate Gradient stabilized iterative matrix solver.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The Bi-Conjugate Gradient Stabilized (BiCGStab) solver is an 'improvement'
    /// of the standard Conjugate Gradient (CG) solver. Unlike the CG solver the
    /// BiCGStab can be used on non-symmetric matrices. <br/>
    /// Note that much of the success of the solver depends on the selection of the
    /// proper preconditioner.
    /// </para>
    /// <para>
    /// The Bi-CGSTAB algorithm was taken from: <br/>
    /// Templates for the solution of linear systems: Building blocks
    /// for iterative methods
    /// <br/>
    /// Richard Barrett, Michael Berry, Tony F. Chan, James Demmel,
    /// June M. Donato, Jack Dongarra, Victor Eijkhout, Roldan Pozo,
    /// Charles Romine and Henk van der Vorst
    /// <br/>
    /// Url: <a href="http://www.netlib.org/templates/Templates.html">http://www.netlib.org/templates/Templates.html</a>
    /// <br/>
    /// Algorithm is described in Chapter 2, section 2.3.8, page 27
    /// </para>
    /// <para>
    /// The example code below provides an indication of the possible use of the
    /// solver.
    /// </para>
    /// </remarks>
    /// <include file='../../../../examples/LinearAlgebra/Solvers/Iterative/BiCgStab.xml' path='example'/> 
    public sealed class BiCgStab : IIterativeSolver
    {
        /// <summary>
        /// The status used if there is no status, i.e. the solver hasn't run yet and there is no
        /// iterator.
        /// </summary>
        private static readonly ICalculationStatus sm_DefaultStatus = new CalculationIndetermined();

        /// <summary>
        /// The preconditioner that will be used. Can be set to <c>null</c>, in which case the default
        /// pre-conditioner will be used.
        /// </summary>
        private IPreConditioner m_Preconditioner;

        /// <summary>
        /// The iterative process controller.
        /// </summary>
        private IIterator m_Iterator;

        /// <summary>
        /// Indicates if the user has stopped the solver.
        /// </summary>
        private bool m_HasBeenStopped;
        
        /// <summary>
        /// Initializes a new instance of the <c>BiCgStab</c> class.
        /// </summary>
        /// <remarks>
        /// When using this constructor the solver will use the <see cref="IIterator"/> with
        /// the standard settings and a default preconditioner.
        /// </remarks>
        public BiCgStab() : this(null, null)
        {}

        /// <summary>
        /// Initializes a new instance of the <c>BiCgStab</c> class.
        /// </summary>
        /// <remarks>
        /// <para>
        /// When using this constructor the solver will use a default preconditioner.
        /// </para>
        /// <para>
        /// The main advantages of using a user defined <see cref="IIterator"/> are:
        /// <list type="number">
        /// <item>It is possible to set the desired convergence limits.</item>
        /// <item>
        /// It is possible to check the reason for which the solver finished 
        /// the iterative procedure by calling the <see cref="IIterator.Status"/> property.
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        /// <param name="iterator">
        ///   The <see cref="IIterator"/> that will be used to monitor the iterative process.
        /// </param>
        public BiCgStab(IIterator iterator) : this(null, iterator)
        {}

        /// <summary>
        /// Initializes a new instance of the <c>BiCgStab</c> class.
        /// </summary>
        /// <remarks>
        /// When using this constructor the solver will use the <see cref="IIterator"/> with
        /// the standard settings.
        /// </remarks>
        /// <param name="preconditioner">
        /// The <see cref="IPreConditioner"/> that will be used to precondition the matrix equation.
        /// </param>
        public BiCgStab(IPreConditioner preconditioner) : this(preconditioner, null)
        {}

        /// <summary>
        /// Initializes a new instance of the <c>BiCgStab</c> class.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The main advantages of using a user defined <see cref="IIterator"/> are:
        /// <list type="number">
        /// <item>It is possible to set the desired convergence limits.</item>
        /// <item>
        /// It is possible to check the reason for which the solver finished 
        /// the iterative procedure by calling the <see cref="IIterator.Status"/> property.
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        /// <param name="preconditioner">
        /// The <see cref="IPreConditioner"/> that will be used to precondition the matrix equation.
        /// </param>
        /// <param name="iterator">
        /// The <see cref="IIterator"/> that will be used to monitor the iterative process.
        /// </param>
        public BiCgStab(IPreConditioner preconditioner, IIterator iterator)
        {
            m_Iterator = iterator;
            m_Preconditioner = preconditioner;
        }

        /// <summary>
        /// Sets the <c>IPreConditioner</c> that will be used to precondition the iterative process.
        /// </summary>
        /// <param name="preconditioner">The preconditioner.</param>
        public void SetPreconditioner(IPreConditioner preconditioner)
        {
            m_Preconditioner = preconditioner;
        }

        /// <summary>
        /// Sets the <c>IIterator</c> that will be used to track the iterative process.
        /// </summary>
        /// <param name="iterator">The iterator.</param>
        public void SetIterator(IIterator iterator)
        {
            m_Iterator = iterator;
        }

        /// <summary>
        /// Gets the status of the iteration once the calculation is finished.
        /// </summary>
        public ICalculationStatus IterationResult
        {
            get 
            { 
                return (m_Iterator != null) ? m_Iterator.Status : sm_DefaultStatus; 
            }
        }

        /// <summary>
        /// Stops the solve process. 
        /// </summary>
        /// <remarks>
        /// Note that it may take an indetermined amount of time for the solver to actually
        /// stop the process.
        /// </remarks>
        public void StopSolve()
        {
            m_HasBeenStopped = true;
        }

        /// <summary>
        /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the
        /// solution vector and x is the unknown vector.
        /// </summary>
        /// <param name="matrix">The coefficient matrix, <c>A</c>.</param>
        /// <param name="vector">The solution vector, <c>b</c>.</param>
        /// <returns>The result vector, <c>x</c>.</returns>
        public Vector Solve(Matrix matrix, Vector vector)
        {
            if (vector == null)
            {
                throw new ArgumentNullException();
            }

            Vector result = new DenseVector(matrix.Rows);
            Solve(matrix, vector, result);
            return result;
        }

        /// <summary>
        /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the
        /// solution vector and x is the unknown vector.
        /// </summary>
        /// <param name="matrix">The coefficient matrix, <c>A</c>.</param>
        /// <param name="input">The solution vector, <c>b</c></param>
        /// <param name="result">The result vector, <c>x</c></param>
        public void Solve(Matrix matrix, Vector input, Vector result)
        {
            // If we were stopped before, we are no longer
            // We're doing this at the start of the method to ensure
            // that we can use these fields immediately.
            {
                m_HasBeenStopped = false;
            }

            // Error checks
            {
                if (matrix == null)
                {
                    throw new ArgumentNullException("matrix");
                }

                if (matrix.Rows != matrix.Columns)
                {
                    throw new MatrixNotSquareException();
                }

                if (input == null)
                {
                    throw new ArgumentNullException("input");
                }

                if (result == null)
                {
                    throw new ArgumentNullException("result");
                }

                if (result.Count != input.Count)
                {
                    throw new NotConformableException();
                }
            }

            // Initialize the solver fields
            {
                // Set the convergence monitor
                if (m_Iterator == null)
                {
                    m_Iterator = Iterator.CreateDefault();
                }

                if (m_Preconditioner == null)
                {
                    m_Preconditioner = new UnitPreconditioner();
                }
                m_Preconditioner.Initialize(matrix);
            }

            // Compute r_0 = b - Ax_0 for some initial guess x_0
            // In this case we take x_0 = vector
            // This is basically a SAXPY so it could be made a lot faster
            Vector residuals = new DenseVector(matrix.Rows);
            CalculateTrueResidual(matrix, residuals, result, input);

            //Choose r~ (for example, r~ = r_0)
            Vector tempResiduals = residuals.Clone();
            Vector temp = result.Clone();

            // create five temporary vectors needed to hold temporary
            // coefficients. All vectors are mangled in each iteration.
            // These are defined here to prevent stressing the garbage collector
            Vector p = new DenseVector(residuals.Count);
            Vector pDash = new DenseVector(residuals.Count);
            Vector nu = new DenseVector(residuals.Count);
            Vector s = new DenseVector(residuals.Count);
            Vector sDash = new DenseVector(residuals.Count);
            Vector t = new DenseVector(residuals.Count);
            
            // create some temporary double variables that are needed
            // to hold values in between iterations
            double currentRho = 0;
            double alpha = 0;
            double omega = 0;

            int iterationNumber = 0;
            while (ShouldContinue(iterationNumber, result, input, residuals))
            {
                // rho_(i-1) = r~^T r_(i-1) // dotproduct r~ and r_(i-1)
                double oldRho = currentRho;
                currentRho = tempResiduals.DotProduct(residuals);

                // if (rho_(i-1) == 0) // METHOD FAILS
                // If rho is only 1 ULP from zero then we fail.
                if (Precision.EqualsWithTolerance(currentRho, 0, Constants.UlpsForComparison))
                {
                    // Rho-type breakdown
                    throw new IterativeSolverBrakedownException();
                }

                if (iterationNumber != 0)
                {
                    // beta_(i-1) = (rho_(i-1)/rho_(i-2))(alpha_(i-1)/omega(i-1))
                    double beta = (currentRho / oldRho) * (alpha / omega);
                    // p_i = r_(i-1) + beta_(i-1)(p_(i-1) - omega_(i-1) * nu_(i-1))
                    p.AddScaledVector(-omega, nu);
                   
                    p.Multiply(beta);
                    p.Add(residuals);
                }
                else
                {
                    // p_i = r_(i-1)
                    residuals.CopyTo(p);
                }

                // SOLVE Mp~ = p_i // M = preconditioner
                m_Preconditioner.Approximate(p, pDash);
                // nu_i = Ap~
                matrix.Multiply(pDash, nu);

                // alpha_i = rho_(i-1)/ (r~^T nu_i) = rho / dotproduct(r~ and nu_i)
                alpha = currentRho * 1 / tempResiduals.DotProduct(nu);

                // s = r_(i-1) - alpha_i nu_i
                residuals.AddScaledVector(-alpha, nu, s);

                // Check if we're converged. If so then stop. Otherwise continue;
                // Calculate the temporary result. 
                // Be careful not to change any of the temp vectors, except for
                // temp. Others will be used in the calculation later on.
                // x_i = x_(i-1) + alpha_i * p^_i + s^_i
                pDash.Multiply(alpha, temp);
                temp.Add(sDash);
                temp.Add(result);

                // Check convergence and stop if we are converged.
                if (!ShouldContinue(iterationNumber, temp, input, s))
                {
                    temp.CopyTo(result);

                    // Calculate the true residual
                    CalculateTrueResidual(matrix, residuals, result, input);

                    // Now recheck the convergence
                    if (!ShouldContinue(iterationNumber, result, input, residuals))
                    {
                        // We're all good now.
                        return;
                    }
                    else
                    {
                        // Continue the calculation
                        iterationNumber++;
                        continue;
                    }
                }

                // SOLVE Ms~ = s
                m_Preconditioner.Approximate(s, sDash);

                // temp = As~
                matrix.Multiply(sDash, t);

                // omega_i = temp^T s / temp^T temp
                omega = t.DotProduct(s) / t.DotProduct();

                // x_i = x_(i-1) + alpha_i p^ + omega_i s^
                result.AddScaledVector(omega, sDash);
                result.AddScaledVector(alpha, pDash);

                // ??
                t.Multiply(-omega, residuals);
                residuals.Add(s);

                // for continuation it is necessary that omega_i != 0.0
                // If omega is only 1 ULP from zero then we fail.
                if (Precision.EqualsWithTolerance(omega, 0, Constants.UlpsForComparison))
                {
                    // Omega-type breakdown
                    throw new IterativeSolverBrakedownException();
                }

                if (!ShouldContinue(iterationNumber, result, input, residuals))
                {
                    // Recalculate the residuals and go round again. This is done to ensure that
                    // we have the proper residuals.
                    // The residual calculation based on omega_i * s can be off by a factor 10. So here
                    // we calculate the real residual (which can be expensive) but we only do it if we're
                    // sufficiently close to the finish.
                    CalculateTrueResidual(matrix, residuals, result, input);
                }

                iterationNumber++;
            }
        }

        // Calculates the true residual of the matrix equation Ax = b according to:
        // residual = b - Ax
        private void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b)
        {
            // -Ax = residual
            matrix.Multiply(x, residual);
            residual.Negate();

            // residual + b
            residual.Add(b);
        }

        private bool ShouldContinue(int iterationNumber, Vector result, Vector source, Vector residuals)
        {
            if (m_HasBeenStopped)
            {
                m_Iterator.IterationCancelled();
                return true;
            }

            m_Iterator.DetermineStatus(iterationNumber, result, source, residuals);
            ICalculationStatus status = m_Iterator.Status;

            // We stop if either:
            // - the user has stopped the calculation
            // - the calculation needs to be stopped from a numerical point of view (divergence, convergence etc.)
            return (!status.TerminatesCalculation) && (!m_HasBeenStopped);
        }

        /// <summary>
        /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the
        /// solution matrix and X is the unknown matrix.
        /// </summary>
        /// <param name="matrix">The coefficient matrix, <c>A</c>.</param>
        /// <param name="input">The solution matrix, <c>B</c>.</param>
        /// <returns>The result matrix, <c>X</c>.</returns>
        public Matrix Solve(Matrix matrix, Matrix input)
        {
            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            Matrix result = matrix.CreateMatrix(input.Rows, input.Columns);
            Solve(matrix, input, result);
            return result;
        }

        /// <summary>
        /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the
        /// solution matrix and X is the unknown matrix.
        /// </summary>
        /// <param name="matrix">The coefficient matrix, <c>A</c>.</param>
        /// <param name="input">The solution matrix, <c>B</c>.</param>
        /// <param name="result">The result matrix, <c>X</c></param>
        public void Solve(Matrix matrix, Matrix input, Matrix result)
        {
            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            if (matrix.Rows != input.Rows || input.Rows != result.Rows || input.Columns != result.Columns)
            {
                throw new NotConformableException();
            }

            for (int column = 0; column < input.Columns; column++)
            {
                Vector solution = Solve(matrix, input.GetColumn(column));
                foreach (KeyValuePair<int, double> element in solution.GetIndexedEnumerator())
                {
                    result.ValueAt(element.Key, column, element.Value);
                }
            }
        }
    }
}
